/**
 * ***************************************************************************** Copyright (c) 2003,
 * 2006 Subclipse project and others. All rights reserved. This program and the accompanying
 * materials are made available under the terms of the Eclipse Public License v1.0 which accompanies
 * this distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * <p>Contributors: Subclipse project committers - initial API and implementation
 * ****************************************************************************
 */
package org.tigris.subversion.subclipse.ui;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Plugin;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.content.IContentDescription;
import org.eclipse.core.runtime.content.IContentTypeManager;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.team.core.TeamException;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;
import org.osgi.service.prefs.BackingStoreException;
import org.tigris.subversion.subclipse.core.ISVNRemoteFile;
import org.tigris.subversion.subclipse.core.ISVNRemoteFolder;
import org.tigris.subversion.subclipse.core.ISVNRepositoryLocation;
import org.tigris.subversion.subclipse.core.SVNClientManager;
import org.tigris.subversion.subclipse.core.SVNProviderPlugin;
import org.tigris.subversion.subclipse.core.SVNStatus;
import org.tigris.subversion.subclipse.core.resources.BaseResourceStorageFactory;
import org.tigris.subversion.subclipse.ui.actions.SVNPluginAction;
import org.tigris.subversion.subclipse.ui.actions.ShowOutOfDateFoldersAction;
import org.tigris.subversion.subclipse.ui.actions.WorkspaceAction;
import org.tigris.subversion.subclipse.ui.authentication.SVNPromptUserPassword;
import org.tigris.subversion.subclipse.ui.compare.UIBaseResourceStorageFactory;
import org.tigris.subversion.subclipse.ui.conflicts.MergeFileAssociation;
import org.tigris.subversion.subclipse.ui.console.SVNOutputConsole;
import org.tigris.subversion.subclipse.ui.repository.RepositoryManager;
import org.tigris.subversion.subclipse.ui.repository.model.SVNAdapterFactory;
import org.tigris.subversion.subclipse.ui.util.SimpleDialogsHelper;
import org.tigris.subversion.svnclientadapter.ISVNClientAdapter;

/** UI Plugin for Subversion provider-specific workbench functionality. */
public class SVNUIPlugin extends AbstractUIPlugin {
  /** The id of the SVN plug-in */
  public static final String ID = "org.tigris.subversion.subclipse.ui"; // $NON-NLS-1$

  public static final String DECORATOR_ID =
      "org.tigris.subversion.subclipse.ui.decorator"; //$NON-NLS-1$
  public static final String PROVIDER_ID =
      "org.tigris.subversion.subclipse.core.svnnature"; //$NON-NLS-1$

  // Merge provider extension point ID
  public static final String MERGE_PROVIDERS =
      "org.tigris.subversion.subclipse.ui.mergeProviders"; //$NON-NLS-1$

  // Commit Dialog extension points
  public static final String COMMIT_DIALOG_TOOLBAR_ACTIONS =
      "org.tigris.subversion.subclipse.ui.commitDialogToolBarActions"; //$NON-NLS-1$
  public static final String COMMIT_DIALOG_COMPARE_ACTIONS =
      "org.tigris.subversion.subclipse.ui.commitDialogCompareActions"; //$NON-NLS-1$

  // Repository source provider extension point ID
  public static final String REPOSITORY_SOURCE_PROVIDERS =
      "org.tigris.subversion.subclipse.ui.svnRepositorySourceProviders"; //$NON-NLS-1$

  /** Property constant indicating the decorator configuration has changed. */
  public static final String P_DECORATORS_CHANGED =
      SVNUIPlugin.ID + ".P_DECORATORS_CHANGED"; // $NON-NLS-1$
  /** The singleton plug-in instance */
  private static SVNUIPlugin plugin;

  private static boolean loadErrorHandled = false;

  /** The repository manager */
  private RepositoryManager repositoryManager;

  private ImageDescriptors imageDescriptors;

  private Preferences preferences;

  private static WorkspaceAction[] mergeProviders;
  private static SVNPluginAction[] commitDialogToolBarActions;
  private static SVNPluginAction[] commitDialogCompareActions;
  private static ISVNRepositorySourceProvider[] repositorySourceProviders;

  //	// Property change listener
  //	IPropertyChangeListener teamUIListener = new IPropertyChangeListener() {
  //		public void propertyChange(PropertyChangeEvent event) {
  //			if (event.getProperty().equals(TeamUI.GLOBAL_IGNORES_CHANGED)) {
  //				SVNLightweightDecorator.refresh();
  //			}
  //		}
  //	};

  private SVNMarkerListener markerListener;

  private ShowOutOfDateFoldersAction showOutOfDateFoldersAction;

  public static final boolean TEST_MODE;

  static {
    String application = System.getProperty("eclipse.application", "");
    if (application.length() > 0) {
      TEST_MODE = application.endsWith("testapplication") || application.endsWith("uitest");
    } else {
      String commands = System.getProperty("eclipse.commands", "");
      TEST_MODE = commands.contains("testapplication\n");
    }
  }

  public static void log(CoreException e) {
    log(e.getStatus().getSeverity(), Policy.bind("simpleInternal"), e); // $NON-NLS-1$
  }

  public static void log(int severity, String message, Throwable e) {
    log(new Status(severity, ID, 0, message, e));
  }

  public SVNUIPlugin() {
    super();
    plugin = this;
  }

  /**
   * Convenience method to get the currently active workbench page. Note that the active page may
   * not be the one that the usr perceives as active in some situations so this method of obtaining
   * the activae page should only be used if no other method is available.
   *
   * @return the active workbench page
   */
  public static IWorkbenchPage getActivePage() {
    IWorkbenchWindow window = getPlugin().getWorkbench().getActiveWorkbenchWindow();
    if (window == null) return null;
    return window.getActivePage();
  }

  /**
   * Creates a busy cursor and runs the specified runnable. May be called from a non-UI thread.
   *
   * @param parent the parent Shell for the dialog
   * @param cancelable if true, the dialog will support cancelation
   * @param runnable the runnable
   * @exception InvocationTargetException when an exception is thrown from the runnable
   * @exception InterruptedException when the progress monitor is cancelled
   */
  public static void runWithProgress(
      Shell parent, boolean cancelable, final IRunnableWithProgress runnable)
      throws InvocationTargetException, InterruptedException {

    boolean createdShell = false;
    try {
      if (parent == null || parent.isDisposed()) {
        Display display = Display.getCurrent();
        if (display == null) {
          // cannot provide progress (not in UI thread)
          runnable.run(new NullProgressMonitor());
          return;
        }
        // get the active shell or a suitable top-level shell
        parent = display.getActiveShell();
        if (parent == null) {
          parent = new Shell(display);
          createdShell = true;
        }
      }
      // pop up progress dialog after a short delay
      final Exception[] holder = new Exception[1];
      BusyIndicator.showWhile(
          parent.getDisplay(),
          new Runnable() {
            public void run() {
              try {
                runnable.run(new NullProgressMonitor());
              } catch (InvocationTargetException e) {
                holder[0] = e;
              } catch (InterruptedException e) {
                holder[0] = e;
              }
            }
          });
      if (holder[0] != null) {
        if (holder[0] instanceof InvocationTargetException) {
          throw (InvocationTargetException) holder[0];
        } else {
          throw (InterruptedException) holder[0];
        }
      }
      // new TimeoutProgressMonitorDialog(parent, TIMEOUT).run(true /*fork*/, cancelable, runnable);
    } finally {
      if (createdShell) parent.dispose();
    }
  }

  /**
   * Returns the singleton plug-in instance.
   *
   * @return the plugin instance
   */
  public static SVNUIPlugin getPlugin() {
    return plugin;
  }

  /**
   * Returns the repository manager
   *
   * @return the repository manager
   */
  public synchronized RepositoryManager getRepositoryManager() {
    if (repositoryManager == null) {
      repositoryManager = new RepositoryManager();
      repositoryManager.startup();
    }
    return repositoryManager;
  }

  /**
   * Convenience method for logging statuses to the plugin log
   *
   * @param status the status to log
   */
  public static void log(IStatus status) {
    getPlugin().getLog().log(status);
  }

  public static void log(String msg) {
    getPlugin().getLog().log(new Status(IStatus.INFO, SVNUIPlugin.ID, 0, msg, null));
  }

  public static void log(TeamException e) {
    getPlugin()
        .getLog()
        .log(
            new Status(
                e.getStatus().getSeverity(),
                SVNUIPlugin.ID,
                0,
                Policy.bind("simpleInternal"),
                e)); //$NON-NLS-1$
  }

  // flags to tailor error reporting
  public static final int PERFORM_SYNC_EXEC = 1;
  public static final int LOG_TEAM_EXCEPTIONS = 2;
  public static final int LOG_CORE_EXCEPTIONS = 4;
  public static final int LOG_OTHER_EXCEPTIONS = 8;
  public static final int LOG_NONTEAM_EXCEPTIONS = LOG_CORE_EXCEPTIONS | LOG_OTHER_EXCEPTIONS;
  private SVNOutputConsole console;
  private URL baseURL;

  /**
   * Convenience method for showing an error dialog
   *
   * @param shell a valid shell or null
   * @param exception the exception to be report
   * @param title the title to be displayed
   * @return IStatus the status that was displayed to the user
   */
  public static IStatus openError(Shell shell, String title, String message, Throwable exception) {
    return openError(shell, title, message, exception, LOG_OTHER_EXCEPTIONS);
  }

  /**
   * Convenience method for showing an error dialog
   *
   * @param shell a valid shell or null
   * @param exception the exception to be report
   * @param title the title to be displayed
   * @param flags customizing attributes for the error handling
   * @return IStatus the status that was displayed to the user
   */
  public static IStatus openError(
      Shell providedShell, String title, String message, Throwable exception, int flags) {
    // Unwrap InvocationTargetExceptions
    if (exception instanceof InvocationTargetException) {
      Throwable target = ((InvocationTargetException) exception).getTargetException();
      // re-throw any runtime exceptions or errors so they can be handled by the workbench
      if (target instanceof RuntimeException) {
        throw (RuntimeException) target;
      }
      if (target instanceof Error) {
        throw (Error) target;
      }
      return openError(providedShell, title, message, target, flags);
    }

    // Determine the status to be displayed (and possibly logged)
    IStatus status = null;
    boolean log = false;
    if (exception instanceof CoreException) {
      status = ((CoreException) exception).getStatus();
      log = ((flags & LOG_CORE_EXCEPTIONS) > 0);
    } else if (exception instanceof TeamException) {
      status = ((TeamException) exception).getStatus();
      log = ((flags & LOG_TEAM_EXCEPTIONS) > 0);
    } else if (exception instanceof InterruptedException) {
      return new SVNStatus(IStatus.OK, Policy.bind("ok")); // $NON-NLS-1$
    } else if (exception != null) {
      status = new SVNStatus(IStatus.ERROR, Policy.bind("internal"), exception); // $NON-NLS-1$
      log = ((flags & LOG_OTHER_EXCEPTIONS) > 0);
      if (title == null) title = Policy.bind("SimpleInternal"); // $NON-NLS-1$
    }

    // Check for a build error and report it differently
    if (status.getCode() == IResourceStatus.BUILD_FAILED) {
      message = Policy.bind("buildError"); // $NON-NLS-1$
      log = true;
    }

    // Check for multi-status with only one child
    if (status.isMultiStatus() && status.getChildren().length == 1) {
      status = status.getChildren()[0];
    }
    if (status.isOK()) return status;

    // Log if the user requested it
    if (log) SVNUIPlugin.log(status);
    // Create a runnable that will display the error status

    String svnInterface =
        SVNUIPlugin.getPlugin().getPreferenceStore().getString(ISVNUIConstants.PREF_SVNINTERFACE);
    boolean loadError =
        svnInterface.equals("javahl")
            && status != null
            && status.getMessage() != null
            && status.getMessage().equals(SVNClientManager.UNABLE_TO_LOAD_DEFAULT_CLIENT);

    if (!loadError || loadErrorHandled) {
      final String displayTitle = title;
      final String displayMessage = message;
      final IStatus displayStatus = status;
      final IOpenableInShell openable =
          new IOpenableInShell() {
            public void open(Shell shell) {
              if (displayStatus.getSeverity() == IStatus.INFO && !displayStatus.isMultiStatus()) {
                MessageDialog.openInformation(
                    shell, Policy.bind("information"), displayStatus.getMessage()); // $NON-NLS-1$
              } else {
                ErrorDialog.openError(shell, displayTitle, displayMessage, displayStatus);
              }
            }
          };
      openDialog(providedShell, openable, flags);
    }

    if (loadError) loadErrorHandled = true;

    // return the status we display
    return status;
  }

  /**
   * Interface that allows a shell to be passed to an open method. The provided shell can be used
   * without sync-execing, etc.
   */
  public interface IOpenableInShell {
    public void open(Shell shell);
  }

  /**
   * Open the dialog code provided in the IOpenableInShell, ensuring that the provided whll is
   * valid. This method will provide a shell to the IOpenableInShell if one is not provided to the
   * method.
   *
   * @param providedShell
   * @param openable
   * @param flags
   */
  public static void openDialog(Shell providedShell, final IOpenableInShell openable, int flags) {
    // If no shell was provided, try to get one from the active window
    if (providedShell == null) {
      IWorkbenchWindow window = SVNUIPlugin.getPlugin().getWorkbench().getActiveWorkbenchWindow();
      if (window != null) {
        providedShell = window.getShell();
        // sync-exec when we do this just in case
        flags = flags | PERFORM_SYNC_EXEC;
      }
    }

    // Create a runnable that will display the error status
    final Shell shell = providedShell;
    Runnable outerRunnable =
        new Runnable() {
          public void run() {
            Shell displayShell;
            if (shell == null) {
              Display display = Display.getCurrent();
              displayShell = new Shell(display);
            } else {
              displayShell = shell;
            }
            openable.open(displayShell);
            if (shell == null) {
              displayShell.dispose();
            }
          }
        };

    // Execute the above runnable as determined by the parameters
    if (shell == null || (flags & PERFORM_SYNC_EXEC) > 0) {
      Display display;
      if (shell == null) {
        display = Display.getCurrent();
        if (display == null) {
          display = Display.getDefault();
        }
      } else {
        display = shell.getDisplay();
      }
      display.syncExec(outerRunnable);
    } else {
      outerRunnable.run();
    }
  }

  public void start(BundleContext ctxt) throws Exception {
    super.start(ctxt);

    mergeProviders = getMergeProviders();

    BaseResourceStorageFactory.setCurrent(new UIBaseResourceStorageFactory());

    SVNAdapterFactory factory = new SVNAdapterFactory();

    Platform.getAdapterManager().registerAdapters(factory, ISVNRemoteFile.class);
    Platform.getAdapterManager().registerAdapters(factory, ISVNRemoteFolder.class);
    Platform.getAdapterManager().registerAdapters(factory, ISVNRepositoryLocation.class);
    //		Platform.getAdapterManager().registerAdapters(factory, RepositoryRoot.class);

    baseURL = ctxt.getBundle().getEntry("/"); // $NON-NLS-1$

    preferences = new Preferences(getPreferenceStore());
    preferences.initializeFromSettings();

    markerListener = new SVNMarkerListener();
    SVNProviderPlugin.addResourceStateChangeListener(markerListener);

    //		// if the global ignores list is changed then update decorators.
    // TeamUI.getSynchronizeManager().addSynchronizeParticipants(new ISynchronizeParticipant[]{new
    // SVNWorkspaceSynchronizeParticipant()});
    try {
      console = new SVNOutputConsole();
    } catch (RuntimeException e) {
      // Don't let the console bring down the SVN UI
      log(IStatus.ERROR, "Errors occurred starting the SVN console", e); // $NON-NLS-1$
    }
    SVNProviderPlugin.getPlugin().setSvnPromptUserPassword(new SVNPromptUserPassword());
    SVNProviderPlugin.getPlugin().setSimpleDialogsHelper(new SimpleDialogsHelper());
    SVNProviderPlugin.getPlugin()
        .setSvnFileModificationValidatorPrompt(new SVNFileModificationValidatorPrompt());

    showOutOfDateFoldersAction = new ShowOutOfDateFoldersAction();
  }

  /** @see Plugin#shutdown() */
  public void stop(BundleContext ctxt) throws Exception {
    super.stop(ctxt);
    //		TeamUI.removePropertyChangeListener(listener);
    try {
      if (repositoryManager != null) repositoryManager.shutdown();
    } catch (TeamException e) {
      throw new CoreException(e.getStatus());
    }

    console.shutdown();
  }

  public void clearPasswordStoresFromConfiguration(boolean restart) throws Exception {
    Exception exception = null;
    File configFile = getConfigFile();
    if (configFile.exists()) {
      File temp = null;
      boolean written = false;
      String newLine = System.getProperty("line.separator");
      BufferedReader input = null;
      BufferedWriter output = null;
      try {
        temp = File.createTempFile("config", null, configFile.getParentFile());
        input = new BufferedReader(new FileReader(configFile));
        output = new BufferedWriter(new FileWriter(temp));
        String line = null;
        boolean authSectionFound = false;
        boolean passwordStoresFound = false;
        boolean inAuthSection = false;
        while ((line = input.readLine()) != null) {
          if (line.startsWith("[auth]")) {
            authSectionFound = true;
            inAuthSection = true;
          } else {
            if (line.startsWith("[")) {
              if (inAuthSection && !passwordStoresFound) {
                output.write("password-stores =" + newLine);
              }
              inAuthSection = false;
            }
          }
          if (line.startsWith("password-stores =")) {
            passwordStoresFound = true;
            if (!line.trim().endsWith("password-stores =")) {
              line = "password-stores =";
            }
          }
          if (line.startsWith("password-stores=")) {
            passwordStoresFound = true;
            if (!line.trim().endsWith("password-stores=")) {
              line = "password-stores=";
            }
          }
          output.write(line + newLine);
        }
        if (!authSectionFound) {
          output.write("[auth]" + newLine + "password-stores =");
        } else {
          if (inAuthSection && !passwordStoresFound) {
            output.write("password-stores =" + newLine);
          }
        }
        written = true;
      } catch (Exception e) {
        exception = e;
      } finally {
        if (input != null) {
          try {
            input.close();
          } catch (IOException e) {
          }
        }
        if (output != null) {
          try {
            output.close();
          } catch (IOException e) {
          }
        }
      }
      if (exception != null) {
        throw exception;
      }
      if (written) {
        configFile.renameTo(new File(configFile.getParentFile(), "config_backup"));
        temp.renameTo(configFile);
        if (restart) {
          PlatformUI.getWorkbench().restart();
        }
      } else {
        if (temp != null && temp.exists()) {
          temp.deleteOnExit();
        }
      }
    }
  }

  public File getConfigFile() {
    File configDir;
    String configDirPath = getPreferenceStore().getString(ISVNUIConstants.PREF_SVNCONFIGDIR);
    if (configDirPath == null || configDirPath.trim().length() == 0) {
      File homeDirectory = new File(System.getProperty("user.home"));
      configDir = new File(homeDirectory, ".subversion");
    } else {
      configDir = new File(configDirPath);
    }
    File configFile = new File(configDir, "config");
    return configFile;
  }

  public String getPasswordStores() {
    String passwordStores = null;
    File configFile = getConfigFile();
    if (configFile.exists()) {
      BufferedReader input = null;
      try {
        input = new BufferedReader(new FileReader(configFile));
        String line = null;
        while ((line = input.readLine()) != null) {
          if (line.startsWith("password-stores =") || line.startsWith("password-stores=")) {
            if (!line.trim().endsWith("=")) {
              int index = line.indexOf("=");
              passwordStores = line.substring(index + 1);
            }
            break;
          }
        }
      } catch (Exception e) {
      } finally {
        if (input != null) {
          try {
            input.close();
          } catch (IOException e) {
          }
        }
      }
    }
    return passwordStores;
  }

  public boolean passwordStoresConfiguredOnLinux() {
    if (System.getProperty("os.name").toLowerCase().indexOf("linux") == -1) {
      return false;
    }
    String svnInterface = getPreferenceStore().getString(ISVNUIConstants.PREF_SVNINTERFACE);
    if (svnInterface != null && !svnInterface.equals("javahl")) {
      return false;
    }
    boolean passwordStoresFound = false;
    boolean valueIsGnomeKeyring = false;
    File configFile = getConfigFile();
    if (!configFile.exists()) {
      ISVNClientAdapter client = null;
      try {
        // This is ja hack to cause the config file to be created so that we
        // can offer to fix it.  We are using a non-existant path so that it will
        // just end quickly.
        client = SVNProviderPlugin.getPlugin().getSVNClientManager().getSVNClient();
        client.cleanup(new File("/This/is/just/a/dummy/file"));
      } catch (Exception e) {
      } finally {
        SVNProviderPlugin.getPlugin().getSVNClientManager().returnSVNClient(client);
      }
    }
    if (!configFile.exists()) return false;
    BufferedReader input = null;
    try {
      input = new BufferedReader(new FileReader(configFile));
      String line = null;
      while ((line = input.readLine()) != null) {
        if (line.startsWith("password-stores =")) {
          passwordStoresFound = true;
          if (!line.trim().endsWith("password-stores =")) {
            valueIsGnomeKeyring = line.indexOf("gnome-keyring") != -1;
          }
          break;
        }
        if (line.startsWith("password-stores=")) {
          passwordStoresFound = true;
          if (!line.trim().endsWith("password-stores=")) {
            valueIsGnomeKeyring = line.indexOf("gnome-keyring") != -1;
          }
          break;
        }
      }
    } catch (Exception e) {
    } finally {
      if (input != null) {
        try {
          input.close();
        } catch (IOException e) {
        }
      }
    }
    return valueIsGnomeKeyring || !passwordStoresFound;
  }

  /** Returns all the commit dialog toolbar actions that were found from the extension point. */
  public static SVNPluginAction[] getCommitDialogToolBarActions() {
    if (commitDialogToolBarActions == null) {
      ArrayList actionsList = new ArrayList();
      IConfigurationElement[] elements =
          Platform.getExtensionRegistry()
              .getConfigurationElementsFor(COMMIT_DIALOG_TOOLBAR_ACTIONS);
      for (int i = 0; i < elements.length; i++) {
        SVNPluginAction action = new SVNPluginAction(elements[i]);
        if (action.getDelegate() != null) {
          actionsList.add(action);
        }
      }
      commitDialogToolBarActions = new SVNPluginAction[actionsList.size()];
      actionsList.toArray(commitDialogToolBarActions);
    }
    return commitDialogToolBarActions;
  }

  /** Returns the commit dialog compare actions found from the extension point. */
  public static SVNPluginAction[] getCommitDialogCompareActions() {
    if (commitDialogCompareActions == null) {
      ArrayList actionsList = new ArrayList();
      IConfigurationElement[] elements =
          Platform.getExtensionRegistry()
              .getConfigurationElementsFor(COMMIT_DIALOG_COMPARE_ACTIONS);
      for (int i = 0; i < elements.length; i++) {
        SVNPluginAction action = new SVNPluginAction(elements[i]);
        if (action.getDelegate() != null) {
          actionsList.add(action);
        }
      }
      commitDialogCompareActions = new SVNPluginAction[actionsList.size()];
      actionsList.toArray(commitDialogCompareActions);
    }
    return commitDialogCompareActions;
  }

  // Initialize the merge providers by searching the registry for users of the
  // mergeProviders extension point.
  public static WorkspaceAction[] getMergeProviders() throws Exception {
    if (mergeProviders == null) {
      ArrayList mergeProviderList = new ArrayList();
      IExtensionRegistry extensionRegistry = Platform.getExtensionRegistry();
      IConfigurationElement[] configurationElements =
          extensionRegistry.getConfigurationElementsFor(MERGE_PROVIDERS);
      for (int i = 0; i < configurationElements.length; i++) {
        IConfigurationElement configurationElement = configurationElements[i];
        WorkspaceAction mergeProvider =
            (WorkspaceAction)
                configurationElement.createExecutableExtension("class"); // $NON-NLS-1$
        mergeProvider.setName(configurationElement.getAttribute("name")); // $NON-NLS-1$
        mergeProviderList.add(mergeProvider);
      }
      mergeProviders = new WorkspaceAction[mergeProviderList.size()];
      mergeProviderList.toArray(mergeProviders);
    }
    return mergeProviders;
  }

  // Initialize the repository source providers by searching the registry for users of the
  // svnRepositorySourceProviders extension point.
  public static ISVNRepositorySourceProvider[] getRepositorySourceProviders() throws Exception {
    if (repositorySourceProviders == null) {
      List<ISVNRepositorySourceProvider> repositorySourceProviderList =
          new ArrayList<ISVNRepositorySourceProvider>();
      IExtensionRegistry extensionRegistry = Platform.getExtensionRegistry();
      IConfigurationElement[] configurationElements =
          extensionRegistry.getConfigurationElementsFor(REPOSITORY_SOURCE_PROVIDERS);
      for (int i = 0; i < configurationElements.length; i++) {
        IConfigurationElement configurationElement = configurationElements[i];
        ISVNRepositorySourceProvider repositorySourceProvider =
            (ISVNRepositorySourceProvider)
                configurationElement.createExecutableExtension("class"); // $NON-NLS-1$
        repositorySourceProvider.setId(configurationElement.getAttribute("id")); // $NON-NLS-1$
        repositorySourceProviderList.add(repositorySourceProvider);
      }
      repositorySourceProviders =
          new ISVNRepositorySourceProvider[repositorySourceProviderList.size()];
      repositorySourceProviderList.toArray(repositorySourceProviders);
      Arrays.sort(
          repositorySourceProviders,
          new Comparator<ISVNRepositorySourceProvider>() {
            public int compare(ISVNRepositorySourceProvider o1, ISVNRepositorySourceProvider o2) {
              return o1.getName().compareTo(o2.getName());
            }
          });
    }
    return repositorySourceProviders;
  }

  public static WorkspaceAction getDefaultMergeProvider() {
    String mergeProvider =
        plugin.getPreferenceStore().getString(ISVNUIConstants.PREF_MERGE_PROVIDER);
    if (mergeProvider != null) {
      for (int i = 0; i < mergeProviders.length; i++) {
        if (mergeProviders[i].getName().equals(mergeProvider)) {
          return mergeProviders[i];
        }
      }
    }
    return mergeProviders[0];
  }

  /**
   * Returns the standard display to be used. The method first checks, if the thread calling this
   * method has an associated display. If so, this display is returned. Otherwise the method returns
   * the default display.
   */
  public static Display getStandardDisplay() {
    Display display = Display.getCurrent();
    if (display == null) {
      display = Display.getDefault();
    }
    return display;
  }

  /**
   * Returns the image descriptor for the given image ID. Returns null if there is no such image.
   */
  public ImageDescriptor getImageDescriptor(String id) {
    if (imageDescriptors == null) {
      imageDescriptors = new ImageDescriptors();
      imageDescriptors.initializeImages(
          baseURL, getPreferenceStore().getInt(ISVNUIConstants.PREF_MENU_ICON_SET));
    }
    return imageDescriptors.getImageDescriptor(id);
  }

  /** Temporarily detach the console from the message source */
  public void disableConsoleListener() {
    SVNProviderPlugin.getPlugin().setConsoleListener(null);
  }

  /** Reconnect the console view to the message source */
  public void enableConsoleListener() {
    SVNProviderPlugin.getPlugin().setConsoleListener(console);
  }

  public static String getCharset(String name, InputStream stream) throws IOException {
    IContentDescription description = getContentDescription(name, stream);
    return description == null ? null : description.getCharset();
  }

  public static IContentDescription getContentDescription(String name, InputStream stream)
      throws IOException {
    // tries to obtain a description for this file contents
    IContentTypeManager contentTypeManager = Platform.getContentTypeManager();
    try {
      return contentTypeManager.getDescriptionFor(stream, name, IContentDescription.ALL);
    } finally {
      if (stream != null)
        try {
          stream.close();
        } catch (IOException e) {
          // Ignore exceptions on close
        }
    }
  }

  /**
   * Gets the one and only SVN console managed by this plugin
   *
   * @return the SVN console
   */
  public SVNOutputConsole getConsole() {
    return console;
  }

  public ShowOutOfDateFoldersAction getShowOutOfDateFoldersAction() {
    return showOutOfDateFoldersAction;
  }

  public org.osgi.service.prefs.Preferences getInstancePreferences() {
    return new InstanceScope().getNode(getBundle().getSymbolicName());
  }

  public MergeFileAssociation[] getMergeFileAssociations() throws BackingStoreException {
    ArrayList associations = new ArrayList();
    String[] childrenNames = MergeFileAssociation.getParentPreferences().childrenNames();
    for (int i = 0; i < childrenNames.length; i++) {
      org.osgi.service.prefs.Preferences node =
          MergeFileAssociation.getParentPreferences().node(childrenNames[i]);
      MergeFileAssociation association = new MergeFileAssociation();
      association.setFileType(childrenNames[i]);
      association.setMergeProgram(node.get("mergeProgram", "")); // $NON-NLS-1$,  //$NON-NLS-1$
      association.setParameters(node.get("parameters", "")); // $NON-NLS-1$,  //$NON-NLS-1$
      association.setType(node.getInt("type", MergeFileAssociation.BUILT_IN));
      associations.add(association);
    }
    MergeFileAssociation[] associationArray = new MergeFileAssociation[associations.size()];
    associations.toArray(associationArray);
    Arrays.sort(associationArray);
    return associationArray;
  }

  public MergeFileAssociation getMergeFileAssociation(String fileName)
      throws BackingStoreException {
    MergeFileAssociation[] mergeFileAssociations = getMergeFileAssociations();
    for (int i = 0; i < mergeFileAssociations.length; i++) {
      if (mergeFileAssociations[i].getFileType().equals(fileName)) return mergeFileAssociations[i];
    }
    for (int i = 0; i < mergeFileAssociations.length; i++) {
      if (mergeFileAssociations[i].matches(fileName)) return mergeFileAssociations[i];
    }
    MergeFileAssociation mergeFileAssociation = new MergeFileAssociation();
    IPreferenceStore preferenceStore = getPreferenceStore();
    if (preferenceStore.getBoolean(ISVNUIConstants.PREF_MERGE_USE_EXTERNAL))
      mergeFileAssociation.setType(MergeFileAssociation.DEFAULT_EXTERNAL);
    else mergeFileAssociation.setType(MergeFileAssociation.BUILT_IN);
    return mergeFileAssociation;
  }

  public static Image getImage(String key) {
    return getPlugin().getImageRegistry().get(key);
  }

  protected void initializeImageRegistry(ImageRegistry reg) {
    super.initializeImageRegistry(reg);
    reg.put(ISVNUIConstants.IMG_PROPERTIES, getImageDescriptor(ISVNUIConstants.IMG_PROPERTIES));
    reg.put(
        ISVNUIConstants.IMG_FILEADD_PENDING,
        getImageDescriptor(ISVNUIConstants.IMG_FILEADD_PENDING));
    reg.put(ISVNUIConstants.IMG_SYNCPANE, getImageDescriptor(ISVNUIConstants.IMG_SYNCPANE));
    reg.put(
        ISVNUIConstants.IMG_FILEDELETE_PENDING,
        getImageDescriptor(ISVNUIConstants.IMG_FILEDELETE_PENDING));
    reg.put(
        ISVNUIConstants.IMG_FILEMODIFIED_PENDING,
        getImageDescriptor(ISVNUIConstants.IMG_FILEMODIFIED_PENDING));
    reg.put(
        ISVNUIConstants.IMG_FOLDERADD_PENDING,
        getImageDescriptor(ISVNUIConstants.IMG_FOLDERADD_PENDING));
    reg.put(
        ISVNUIConstants.IMG_FOLDERDELETE_PENDING,
        getImageDescriptor(ISVNUIConstants.IMG_FOLDERDELETE_PENDING));
    reg.put(
        ISVNUIConstants.IMG_FOLDERMODIFIED_PENDING,
        getImageDescriptor(ISVNUIConstants.IMG_FOLDERMODIFIED_PENDING));
    reg.put(ISVNUIConstants.IMG_FOLDER, getImageDescriptor(ISVNUIConstants.IMG_FOLDER));
    reg.put(
        ISVNUIConstants.IMG_AFFECTED_PATHS_COMPRESSED_MODE,
        getImageDescriptor(ISVNUIConstants.IMG_AFFECTED_PATHS_COMPRESSED_MODE));
    reg.put(
        ISVNUIConstants.IMG_AFFECTED_PATHS_FLAT_MODE,
        getImageDescriptor(ISVNUIConstants.IMG_AFFECTED_PATHS_FLAT_MODE));
    reg.put(
        ISVNUIConstants.IMG_AFFECTED_PATHS_TREE_MODE,
        getImageDescriptor(ISVNUIConstants.IMG_AFFECTED_PATHS_TREE_MODE));
    reg.put(ISVNUIConstants.IMG_UPDATE_ALL, getImageDescriptor(ISVNUIConstants.IMG_UPDATE_ALL));
    reg.put(ISVNUIConstants.IMG_COMMIT_ALL, getImageDescriptor(ISVNUIConstants.IMG_COMMIT_ALL));
    reg.put(
        ISVNUIConstants.IMG_PROPERTY_CONFLICTED,
        getImageDescriptor(ISVNUIConstants.IMG_PROPERTY_CONFLICTED));
  }

  // Deletes all files and subdirectories under dir.
  // Returns true if all delete was successful.
  private boolean deleteFolder(File folder) {
    if (folder.isDirectory()) {
      String[] children = folder.list();
      for (int i = 0; i < children.length; i++) {
        if (!deleteFolder(new File(folder, children[i]))) {
          return false;
        }
      }
    }

    // The folder should be empty so delete it
    return folder.delete();
  }
}
